Advanced Solidity Concepts


Slide 1: Assembly

Example 1: Basic Inline Assembly Block

A simple example of using inline assembly to perform arithmetic operations like addition.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract AssemblyExample {
    function add(uint a, uint b) public pure returns (uint result) {
        assembly {
            // Inline assembly block
            result := add(a, b)
        }
    }
}

In this example, the assembly block performs an addition using the add opcode of the EVM, where a and b are passed into the inline assembly. The := operator is used to assign the result of the addition.


Example 2: Accessing Storage Using Assembly

In this example, we directly access and manipulate storage using inline assembly.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract StorageExample {
    uint256 public storedData;

    function setStoredData(uint256 x) public {
        storedData = x;
    }

    function getStoredData() public view returns (uint256) {
        uint256 result;
        assembly {
            // Access the first storage slot where `storedData` is located
            result := sload(0)
        }
        return result;
    }
}

Here, the sload assembly instruction is used to read from the first storage slot (0), where the state variable storedData is stored.


Example 3: Conditional Logic with Inline Assembly

This example shows how to implement basic conditional logic using inline assembly, such as an if condition.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ConditionalAssembly {
    function isEven(uint256 number) public pure returns (bool) {
        bool result;
        assembly {
            // Check if the number is even by performing a modulus operation
            // and comparing it to zero.
            result := iszero(mod(number, 2))
        }
        return result;
    }
}

Here, the inline assembly checks if the number is even by calculating mod(number, 2) and using the iszero opcode, which returns true if the result is zero (i.e., the number is even).


Example 4: Loop in Inline Assembly

This example demonstrates how to use loops in inline assembly to compute the factorial of a number.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract LoopAssembly {
    function factorial(uint256 n) public pure returns (uint256 result) {
        result = 1; // Initial value for factorial computation
        assembly {
            let i := 1
            for { } lt(i, add(n, 1)) { i := add(i, 1) } {
                result := mul(result, i)
            }
        }
    }
}

Here, we use an inline assembly for loop to calculate the factorial of a number n. The loop starts from i = 1 and multiplies result by i in each iteration.


Example 5: Calling Functions Using Inline Assembly

This example shows how to call an external contract’s function using inline assembly.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract ExternalContract {
    function getValue() external pure returns (uint256) {
        return 42;
    }
}

contract CallExternalAssembly {
    function callExternal(address _contract) public view returns (uint256 result) {
        assembly {
            let ptr := mload(0x40) // Get free memory pointer
            mstore(ptr, 0x4d3c70bc) // Function selector for getValue() (first 4 bytes of the keccak256 hash)
            let success := staticcall(5000, _contract, ptr, 0x04, ptr, 0x20)
            result := mload(ptr) // Load the returned data
        }
    }
}

In this example, we use staticcall in inline assembly to call the getValue() function of an external contract. We manually specify the function selector for getValue() using its first four bytes from the keccak256 hash.


Example 6: Error Handling with revert

Here’s an example of how to handle errors and revert transactions using inline assembly.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract RevertAssembly {
    function checkCondition(uint256 value) public pure returns (string memory) {
        assembly {
            // If the value is less than 10, revert with an error message.
            if lt(value, 10) {
                // Error message: "Value too small"
                let ptr := mload(0x40)
                mstore(ptr, 0x56616c756520746f6f20736d616c6c0000000000000000000000000000000000) // "Value too small" in hex
                revert(ptr, 16)
            }
        }
        return "Value is fine";
    }
}

In this example, if the input value is less than 10, the transaction reverts with an error message using the revert instruction. The error message “Value too small” is stored in memory and passed to the revert call.


Example 7: Returning Multiple Values

This example demonstrates returning multiple values from inline assembly.

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MultipleReturnsAssembly {
    function sumAndProduct(uint256 a, uint256 b) public pure returns (uint256 sum, uint256 product) {
        assembly {
            sum := add(a, b)
            product := mul(a, b)
        }
    }
}

In this case, the assembly block calculates both the sum and product of two numbers and returns them as multiple values.


Summary


Slide 2: Internal Workings of Solidity


Slide 2.1: Layout of State Variables in Storage


Slide 2.2: Transient Storage (Stack)


Slide 2.3: Layout in Memory


Slide 2.4: Memory Layout Example


Slide 2.5: Layout of Call Data


Slide 2.6: Cleaning Up Variables in Solidity


Slide 2.7: Contract Metadata


Slide 2.8: Contract ABI Specification


Slide 2.9: Example of ABI in Use


Slide 2.10: Summary